//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <ddk.h>
#include <drvpub.h>
#include "audiocard.h"

class CAudioCard : public CDeviceDriver
{
public:
    CARAPI Read(
                    /* [in] */ Int64 offset,
                    /* [in] */ Int32 bytesToRead,
                    /* [out] */ MemoryBuf * pBuffer,
                    /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Write(
                    /* [in] */ Int64 offset,
                    /* [in] */ const MemoryBuf &buffer,
                    /* [out] */ Int32 * pBytesWritten,
                    /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Control(
                    /* [in] */ Handle32 controlCode,
                    /* [in] */ const MemoryBuf &inBuffer,
                    /* [out] */ MemoryBuf * pOutBuffer,
                    /* [out] */ IEvent * * ppCompletionEvent);

    virtual void Dispose();

    AudioContext m_audioCxt;
    DzMutex   m_lock;
    // internal counters for power management
    int m_writeCounter;
    int m_readCounter;
};

static CAudioCard s_audio;

ECode CAudioCard::Read(
                /* [in] */ Int64 offset,
                /* [in] */ Int32 bytesToRead,
                /* [out] */ MemoryBuf * pBuffer,
                /* [out] */ IEvent * * ppCompletionEvent)
{
    if (pBuffer == NULL) {
        return E_INVALID_ARGUMENT;
    }
    void *pIn = pBuffer->GetPayload();
    UInt32 uSizeRead;
    ECode ec;

    DBGOUT(DBG_READ, ("CAudioCard::Read, pIn: 0x%x, uSizeRead: 0x%x\n",
        pIn, bytesToRead));

    WaitResult wr;
    m_lock.Lock(&wr);

    m_readCounter++;

    ec = AI_Read(&m_audioCxt, pIn, bytesToRead, &uSizeRead);
    if (FAILED(ec)) {
        m_lock.Unlock();
        return ec;
    }

    pBuffer->SetUsed(uSizeRead);
    m_lock.Unlock();

    return NOERROR;
}

ECode CAudioCard::Write(
                /* [in] */ Int64 offset,
                /* [in] */ const MemoryBuf &buffer,
                /* [out] */ Int32 * pBytesWritten,
                /* [out] */ IEvent * * ppCompletionEvent)
{
    void *pOut = buffer.GetPayload();
    UInt32 uSizeWrite = buffer.GetUsed();
    ECode ec;

    DBGOUT(DBG_WRITE, ("CAudioCard::Write, pOut: 0x%x, uSizeWrite: 0x%x\n",
        pOut, uSizeWrite));

    WaitResult wr;
    m_lock.Lock(&wr);

    m_writeCounter++;

    ec = AO_Write(&m_audioCxt, (void *)pOut, uSizeWrite, (UInt32*)pBytesWritten);

    m_lock.Unlock();

    return ec;
}

void power_off_codec( void );
void PoSetAudioPowerOn(Boolean PowerOn);

ECode CAudioCard::Control(
                /* [in] */ Handle32 controlCode,
                /* [in] */ const MemoryBuf &inBuffer,
                /* [out] */ MemoryBuf * pOutBuffer,
                /* [out] */ IEvent * * ppCompletionEvent)
{
    ECode ec = NOERROR;
    WaitResult wr;
    AudioContext *pCxt = &m_audioCxt;
    Byte *pInData;
    int inDataSize;

    //cprintf("+++++++++CAudioCard::Control %d\n", nControlCode);
    DBGOUT(DBG_LOG, ("CAudioCard::Control, controlCode = 0x%x(%d)\n",
        controlCode, controlCode));

    inDataSize = inBuffer.IsNullOrEmpty() ? 0 : inBuffer.GetUsed();
    pInData = inBuffer.IsNullOrEmpty() ? NULL : inBuffer.GetPayload();

    m_lock.Lock(&wr);

    if (controlCode != 0x22 && controlCode != 0x23) {
        Ac97_WarmReset(pCxt, AUXSTATE_ACLINK_AUDIO);
    }

    switch (controlCode) {
    ////////////////////////////////////////////////////////////////////////////
    //
    // Audiocard State 1: STATE_AUDIOOUT
    //
    case 0:
        /*
         *  Sample rate & Channels
         *  Format: bit0-bit31 Sample Rate
                    bit32-bit47 Bytes Per Sample
                    bit48-bit63 Number of Channels(1 & 2)
         */
        if (inDataSize < (int)sizeof(Audio_Params)) {
            ec = E_OUT_OF_MEMORY;
            break;
        }

        ec = AO_Open(pCxt, (Audio_Params *)pInData);

        m_writeCounter = 0;

        break;

    case 1: /* Master Volume */
        if (inDataSize < 1) {
            ec = E_OUT_OF_MEMORY;
            break;
        }
        ec = AO_SetVolume(pCxt, *pInData, *pInData);
        break;

    case 2: /* Voice Channel Volume(s) */
        if (inDataSize < 2) {
            ec = E_OUT_OF_MEMORY;
            break;
        }
        ec = AO_SetVolume(pCxt, *pInData, *(pInData + 1));
        break;

    case 3: {// Get playing volume
        if (pOutBuffer == NULL) {
            ec = E_INVALID_ARGUMENT;
            break;
        }
        if (pOutBuffer->GetCapacity() < (int)sizeof(Byte) * 2) {
            ec = E_OUT_OF_MEMORY;
            break;
        }

        Byte *pVolLeftOut = pOutBuffer->GetPayload();
        Byte *pVolRightOut = pVolLeftOut + 1;

        AO_GetVolume(pCxt, pVolLeftOut, pVolRightOut);

        pOutBuffer->SetUsed(sizeof(Byte) * 2);
        break;
    }

    case 0x10:
        // Flush the buffers and close audio out device
        AO_Close(pCxt);
        if (m_writeCounter == 0) {
            // When m_writeCounter == 0, power mangement module won't invoke
            // power management code now. So should invoke power management
            // routine here.
            DBGOUT(DBG_LOG, ("Close power here when m_writeCounter == 0. "\
                "Please check why entering here.\n"));
            Power_Close(pCxt);
        }
        break;

    case 0x11:
        // Reset audio out device
        AO_Reset(pCxt);
        break;

    case 0x20: {
        // Open the wave out device: speaker/headset/bluetooth headset
        UInt32 device;

        device = (0 == inDataSize) ?
            AODEVICE_SPEAKER : (UInt32)(*pInData);

        ec = AO_SelectDevice(pCxt, device);
        break;
    }

    case 0x21: {
        // Close the wave out device: speaker/headset/bluetooth headset
        UInt32 device;

        device = (0 == inDataSize) ?
            AODEVICE_SPEAKER : (UInt32)(*pInData);

        ec = AO_CloseDevice(pCxt, device);
        break;
    }

    case 0x22:
        // Start vibration
        Gpio_SetOutHigh(89);
        NotifyByIsr(EVENT_INPUT, 0, NULL);//Emulate input event, light LCD
        break;

    case 0x23:
        // Stop vibration.
        Gpio_SetOutLow(89);
        break;

    ////////////////////////////////////////////////////////////////////////////
    //
    // Audiocard State 2: STATE_AUDIOIN
    //
    case 0x80:
        // Recording params
        /*
         *  Sample rate & Channels
         *  Format: bit0-bit31 Sample Rate
                    bit32-bit47 Bytes Per Sample
                    bit48-bit63 Number of Channels(1 & 2)
         */
        if (inDataSize < (int)sizeof(Audio_Params)) {
            ec = E_OUT_OF_MEMORY;
            break;
        }

        ec = AI_Open(pCxt, (Audio_Params *)pInData);

        m_readCounter = 0;

        break;

    case 0x82: {
        // Set recording volume
        Byte left, right;
        if (inDataSize < 1) {
            ec = E_OUT_OF_MEMORY;
            break;
        }
        left = *pInData;
        if (inDataSize == 1) {
            right = 0;
        }
        else {
            right = *(pInData + 1);
        }
        ec = AI_SetVolume(pCxt, left, right);
        break;
    }

    case 0x83: {// Get recording volume
        if (pOutBuffer->GetCapacity() < (int)sizeof(Byte) * 2) {
            ec = E_OUT_OF_MEMORY;
            break;
        }

        Byte *pVolLeftOut = pOutBuffer->GetPayload();
        Byte *pVolRightOut = pVolLeftOut + 1;

        AI_GetVolume(pCxt, pVolLeftOut, pVolRightOut);

        pOutBuffer->SetUsed(sizeof(Byte) * 2);
        break;
    }

    case 0x90:
        //  Close audio in device
        if (m_audioCxt.state & STATE_AUDIOIN) {
            AI_Close(pCxt);
        }
        if (m_readCounter == 0) {
            // When m_readCounter == 0, power mangement module won't invoke
            // power management code now. So should invoke power management
            // routine here.
            DBGOUT(DBG_LOG, ("Close power here when m_readCounter == 0. "\
                "Please check why entering here.\n"));
            Power_Close(pCxt);
        }
        break;

    case 0x91:
        // Reset audio in device
        AI_Reset(pCxt);
        break;

    case 0xa0: {
        Byte left, right;
        // Select the input source of audio in device
        if (inDataSize < 1) {
            ec = E_OUT_OF_MEMORY;
            break;
        }
        left = *pInData;
        if (1 == inDataSize) right = left;
        else right = *(pInData + 1);
        ec = AI_SelectSource(pCxt, left, right);
        break;
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Audiocard State 3: STATE_CALL
    //
    case 0xb0: {
        UInt32 callMode;
        callMode = (0 == inDataSize) ? CALL_NORMAL : (UInt32)(*pInData);
        ec = Call_Open(pCxt, callMode);
        if (SUCCEEDED(ec)) {
            PoSetAudioPowerOn(TRUE);
        }
        break;
    }

    case 0xb1:
        // Close current call
        Call_Close(pCxt);
        PoSetAudioPowerOn(FALSE);
        break;

    case 0xb2:
        // Set call volume
        if (inDataSize < 1) {
            ec = E_OUT_OF_MEMORY;
            break;
        }
        ec = Call_SetVolume(pCxt, *pInData);
        break;
    ////////////////////////////////////////////////////////////////////////////
    //
    // Audiocard State Getting
    //
    case 0x100: {
        // Get the driver state and capabilities
        if (pOutBuffer == NULL) {
            ec = E_INVALID_ARGUMENT;
            break;
        }
        if (pOutBuffer->GetCapacity() < (int)sizeof(Audio_Status)) {
            ec = E_OUT_OF_MEMORY;
            break;
        }
        ec = Audio_GetState(pCxt, (Audio_Status *)pOutBuffer->GetPayload());
        if (SUCCEEDED(ec)) pOutBuffer->SetUsed(sizeof(Audio_Status));
        break;
    }
    case 0x101: {
        // Get the connected device
        UInt32 device;
        if (pOutBuffer->GetCapacity() < (int)sizeof(UInt32)) {
            ec = E_OUT_OF_MEMORY;
            break;
        }
        device = (pCxt->auxState & AUXSTATE_HEADSETPLUG) ? 1 : 0;
        pOutBuffer->Copy((Byte *)&device, sizeof(UInt32));
        break;
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Power management code
    //
    case 1000:
        Power_Close(pCxt);
        break;

    case 1001:
        Power_Open(pCxt);
        break;

    case 1002:
        Power_Open(pCxt);
        break;

    case 1003:
        power_off_codec();
        break;

#if (_TEST_TYPE == 41)
    case 4100:
        SetOutIsrTime(pOutBuffer);
        break;
    case 4101:
        SetRcvIsrTime(pOutBuffer);
        break;
#endif //_TEST_TYPE == 41

    default:
        ec = E_NOT_IMPLEMENTED;
        break;
    }

    m_lock.Unlock();

    return ec;
}

void CAudioCard::Dispose()
{
}

EXTERN IDeviceDriver * CDECL CreateAudio(uint_t uDeviceNo, void *pvParameter)
{
    //Config vibration gpio
    Gpio_SetDir(89, GPIO_PIN_OUT);
    Gpio_SetAFR(89, GPIO_AS_GPIO);

    ECode ec = Audio_Create(&s_audio.m_audioCxt);
    if (FAILED(ec)) return NULL;

    return &s_audio;
}

// Functions for other drivers.
// Touch driver will also use this.
// Because touch and audio uses the same codec.
ECode _InitWM9713()
{
    return Audio_InitHardware(&s_audio.m_audioCxt);
}

void _OpenACLink()
{
    Ac97_WarmReset(&s_audio.m_audioCxt, AUXSTATE_ACLINK_TOUCH);
}

void _CloseACLink()
{
    Ac97_Close(&s_audio.m_audioCxt, AUXSTATE_ACLINK_TOUCH);
}

Boolean _IsAudioBusy()
{
    //cprintf("Current audio state %x\n", s_audio.m_audioCxt.state);
    return s_audio.m_audioCxt.state &
        (STATE_AUDIOOUT|STATE_AUDIOIN|STATE_CALL);
}

void _AudioLock()
{
    WaitResult wr;
    s_audio.m_lock.Lock(&wr);
}

void _AudioUnlock()
{
    s_audio.m_lock.Unlock();
}

void _HeadsetWake()
{
    Codec_GetHeadsetStatus(&s_audio.m_audioCxt);
}
